﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Quic;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using eLibrary.Constant;

namespace Letter.Net.Quic;

sealed class QuicServer : AQuicHost<QuicServerTransportOptions, QuicClientFeatureOptions>, IQuicServer
{
    public QuicServer(string name) : base(name)
    {
    }

    [RequiresPreviewFeatures]
    private QuicListener listener;
    [RequiresPreviewFeatures]
    private QuicListenerOptions listenerOptions;
    
    private EndPoint bindEndPoint;
    private Task acceptTask;
    private int isStop = Booleans.FALSE;
    

    public override EndPoint BindEndPoint
    {
        get { return this.bindEndPoint; }
    }

    [RequiresPreviewFeatures]
    public override async void Build()
    {
        this.listenerOptions = new QuicListenerOptions()
        {
            ApplicationProtocols = new List<SslApplicationProtocol>()
        };
        
        
        base.Build();
    }

    [RequiresPreviewFeatures]
    public override async Task BindAsync(EndPoint endPoint)
    {
        try
        {
            this.listener = await QuicListener.ListenAsync(listenerOptions);
            this.bindEndPoint = this.listener.LocalEndPoint;
            this.acceptTask = this.AcceptAsync();
        }
        catch (QuicException e) when (e.QuicError == QuicError.AlpnInUse)
        {
            // System.Net.Quic throws different exceptions depending upon the address in use scenario.
            // It throws a QuicException with a status of AlpnInUse when the current process is using the UDP port _and_ the ALPN is the same.
            throw new AddressInUseException(e.Message);
        }
        catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
        {
            // It throws a SocketException with a status of AddressAlreadyInUse when another process is using the UDP port.
            throw new AddressInUseException(e.Message);
        }
    }


    [RequiresPreviewFeatures]
    private async Task AcceptAsync()
    {
        while (this.isStop == Booleans.FALSE)
        {
            try
            {
                var connection = await this.listener.AcceptConnectionAsync();
                
                var conn = new QuicConnection(connection);
            }
            catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted)
            {
               break;
            }
            catch (ObjectDisposedException ex)
            {
                break;
            }
            catch (Exception ex)
            {
                break;
            }
        }
    }

    [RequiresPreviewFeatures]
    public override async Task StopAsync()
    {
        if (Interlocked.CompareExchange(ref this.isStop, Booleans.TRUE, Booleans.FALSE) == Booleans.FALSE)
        {
            try
            {
                this.listener.DisposeAsync();
            }
            catch
            {
            }
            finally
            {
                await this.acceptTask;
            }
        }
    }
}